package org.example.java_gobang.api;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.annotation.Resource;
import org.example.java_gobang.game.*;
import org.example.java_gobang.model.User;
import org.example.java_gobang.model.UserMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.socket.CloseStatus;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.handler.TextWebSocketHandler;

import java.io.IOException;

@Component
public class GameAPI extends TextWebSocketHandler {
    private ObjectMapper objectMapper = new ObjectMapper();

    @Autowired
    private RoomManager roomManager;

    @Autowired
    private OnlineUserManager onlineUserManager;

    //注解用这个注入  因为是数据库的操作
    @Resource
    private UserMapper userMapper;

    @Override
    public void afterConnectionEstablished(WebSocketSession session) throws Exception {
        GameReadyResponse resp = new GameReadyResponse();
        //1. 先获取到用户的身份信息 (之前在WebSocketConfig中拦截了HttpSession用户对象 ,可以直接获取到)
        User user = (User) session.getAttributes().get("user");
        if (user == null) {
            resp.setOk(false);
            resp.setReason("用户尚未登录!");
            session.sendMessage(new TextMessage(objectMapper.writeValueAsString(resp)));  //可以分开写
           // session.close();
            return;
        }
        //2. 判定当前用户是否已经进入房间,(之前写的房间管理器逻辑中可以进行查询 根据用户id)
        Room room = roomManager.getRoomByUserId(user.getUserId());
        if (room == null) {
            //如果用户为空,当前没有找到对应的房间,该玩家还没匹配到
            resp.setOk(false);
            resp.setReason("用户尚未匹配到!");
            session.sendMessage(new TextMessage(objectMapper.writeValueAsString(resp)));
            return;
        }

        //3. 判定当前是否存在多开的情况 (该用户是不是在其他地方进入游戏)
        // 利用前面准备的 OnlineUserManager
        if (onlineUserManager.getFromGameHall(user.getUserId()) != null
         || onlineUserManager.getFromGameRoom(user.getUserId()) != null) {
            //如果一个账号 有两种情况  一种在游戏大厅  另一种在游戏房间 也叫做多开
            resp.setOk(false);
            resp.setReason("禁止多开游戏画面!");
            resp.setMessage("repeatConnection");
            session.sendMessage(new TextMessage(objectMapper.writeValueAsString(resp)));
            return;
        }
       // 4. 设置当前玩家上线
        onlineUserManager.enterGameRoom(user.getUserId(), session);

        // 5. 把两个玩家加入到游戏房间中,
        // 前面创建房间/匹配过程中,是在 game_hall.html页面中完成的
        // 因此前面匹配到对手之后,需要经过页面跳转,来到game_room.html 才算正式进入游戏房间(才算玩家准备好)
        // 当前这个逻辑是在 game_room.html页面加载的时候进行的
        // 执行当前逻辑,说明玩家已经页面跳转成功了!
        //页面跳转 很有可能出现失败这种情况
        synchronized (room) {
            if (room.getUser1() == null) {
                //第一个玩家还没进入房间
                //就把当前连上 websocket 的玩家作为user1,加入房间中,
                room.setUser1(user);
                //把先连入房间的玩家作为先手方
                room.setWhiteUser(user.getUserId());  //灵活设置先手 都可以
                System.out.println("玩家 " + user.getUsername() + " 已经准备就绪! 为玩家1");
                return;
            }
            if (room.getUser2() == null) {
                //如果进入到这个逻辑,说明玩家1已经进入房间,现在要给当前玩家作为玩家2
                room.setUser2(user);
                System.out.println("玩家 " + user.getUsername() + " 已经准备就绪! 为玩家2");


                //当两个玩家都加入成功之后,就要让服务器,给这两个玩家都返回 websocket 的响应数据.
                //当前这两个玩家说,游戏双方都已经准备好了.
                //通知玩家1
                noticeGameReady(room,room.getUser1(),room.getUser2());

                //通知玩家2
                noticeGameReady(room,room.getUser2(),room.getUser1());
                return;
            }
        }


        //6.次数如果还有挽回尝试进入同一个房间 ,就提示报错
        //这种情况虽然理论上不存在,但是为了提高程序的健壮性 加一个判定好一点
        resp.setOk(false);
        resp.setReason("当前房间已经满了,你不能加入房间了!");
        session.sendMessage(new TextMessage(objectMapper.writeValueAsString(resp)));
    }

    //this为自己  that为对手
    private void noticeGameReady(Room room, User thisUser, User thatUser) throws IOException {
        GameReadyResponse resp = new GameReadyResponse();
        resp.setMessage("gameReady");
        resp.setOk(true);
        resp.setReason("");
        resp.setRoomId(room.getRoomId());
        resp.setThisUserId(thisUser.getUserId());
        resp.setThatUserId(thatUser.getUserId());
        resp.setWhiteUser(room.getWhiteUser());

        //把当前的响应数据传送给玩家
        WebSocketSession webSocketSession = onlineUserManager.getFromGameRoom(thisUser.getUserId());
        webSocketSession.sendMessage(new TextMessage(objectMapper.writeValueAsString(resp)));
    }

    @Override
    protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
        //1. 先从session 里拿到用户的身份信息
        User user = (User) session.getAttributes().get("user");
        if (user == null) {
            System.out.println("[handleTextMessage] 当前玩家尚未登录!");
            return;
        }

        //2.根据玩家id获取房间 对象
        Room room = roomManager.getRoomByUserId(user.getUserId());
        //3. 通过room对象来处理这次具体的请求
        room.putChess(message.getPayload());
    }

    @Override
    public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception {
        User user = (User) session.getAttributes().get("user");
        if (user == null) {
            //此处就简单处理,在断开连接的时候就不给客户端返回响应了
            return;
        }
        WebSocketSession exitSession = onlineUserManager.getFromGameRoom(user.getUserId());
        if (session == exitSession) {
            //加上这个判定,目的就是为了避免多开的情况下,第二个用户退出连接动作,导致第一个用户的会话被删除 一样才可以删
            onlineUserManager.exitGameRoom(user.getUserId());
        }
        System.out.println("当前用户 " +user.getUsername() + " 游戏房间连接异常!");

        //通知对手获胜
        noticeThatUserWin(user);
    }

    @Override
    public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
        User user = (User) session.getAttributes().get("user");
        if (user == null) {
            //此处就简单处理,在断开连接的时候就不给客户端返回响应了
            return;
        }
        WebSocketSession exitSession = onlineUserManager.getFromGameRoom(user.getUserId());
        if (session == exitSession) {
            //加上这个判定,目的就是为了避免多开的情况下,第二个用户退出连接动作,导致第一个用户的会话被删除 一样才可以删
            onlineUserManager.exitGameRoom(user.getUserId());
        }
        System.out.println("当前用户 " +user.getUsername() + " 离开游戏房间!");

        //通知对手获胜
        noticeThatUserWin(user);
    }


    private void noticeThatUserWin(User user) throws IOException {
        // 1.根据当前玩家 找到玩家所在的房间
        Room room = roomManager.getRoomByUserId(user.getUserId());
        if (room == null) {
            //这个情况说明房间被释放了  也就没有对手了  游戏结束了
            System.out.println("当前房间已经释放了 不用通知对手了");
            return;
        }

        //2. 根据房间找到对手
        User thatUser = (user == room.getUser1()) ? room.getUser2() : room.getUser1();
        //3. 找到对手的在线状态
        WebSocketSession webSocketSession = onlineUserManager.getFromGameRoom(thatUser.getUserId());
        if (webSocketSession == null) {
            //说明对手也掉线了
            System.out.println("对手也掉线了  ,不用通知");
            return;
        }

        //4. 构造一个响应,来通知对手 你是获胜方
        GameResponse resp = new GameResponse();
        resp.setMessage("putChess");
        resp.setUserId(thatUser.getUserId());
        resp.setWinner(thatUser.getUserId());
        webSocketSession.sendMessage(new TextMessage(objectMapper.writeValueAsString(resp)));


        //5.更新玩家的分数信息
        int winUserId = thatUser.getUserId();
        int loseUserId = user.getUserId();
        userMapper.userWin(winUserId);
        userMapper.userLose(loseUserId);


        //6. 释放房间对象
        roomManager.remove(room.getRoomId(), room.getUser1().getUserId(), room.getUser2().getUserId());
    }
}
